/*
Copyright (c) 2010 Bill Davidsen (wdavidsen@yahoo.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing.Design;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design;
using System.Web.UI.Design.WebControls;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Xml.Serialization;

using SCS.Web.UI.WebControls.Design;

namespace SCS.Web.UI.WebControls
{
    #region Enums
    public enum MenuType { Horizontal = 0, Vertical, Context }
    public enum SelectionHighlightType { ItemOnly = 0, ItemPlusAncestors }

    public enum AccessKeyType { None = 0, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z }
    public enum AccessKeyModifierType { Ctrl = 0, Alt, Ctrl_Alt, Ctrl_Shift }
    public enum MenuMarkerType { Right = 0, left }
    public enum UniqueIdType { Id = 0, Tag }
    #endregion

    public delegate void ItemClickedHandler(object sender, MenuItemClickedEventArgs e);
    public delegate void MenuGroupCreatedHandler(object sender, MenuGroupCreatedEventArgs e);

    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
    [Designer(typeof(SCS.Web.UI.WebControls.Design.MenuControlDesigner))]
    [DefaultProperty("Id"), DefaultEvent("MenuItemClicked")]
    [ToolboxData("<{0}:MultiMenu runat=server></{0}:MultiMenu>")]
    [ParseChildren(true), PersistChildren(false)]
    public sealed class MultiMenu : Control, IPostBackEventHandler, INamingContainer
    {
        #region Constants
        const string commonVersion = "1_1";

        const int offsetTopGroupVertical = 4;
        const int offsetTopGroupHorizontalRight = 4;
        const int offsetTopGroupHorizontalLeft = 2;

        const int offsetSubGroupVertical = 25;
        const int offsetSubGroupHorizontalRight = 4;
        const int offsetSubGroupHorizontalLeft = 12;

        #endregion

        #region Client Side Code

        // browser detection client script
        private string _js_commonScriptLevelVariables = @"
		var _isCSS=(document.body&&document.body.style)?true:false;
		var _isIE5={0};
		var _isNN4={1};
		var _isNN6={2};
		var _isOP7={3};
		var _isDOM={4};
		var _isFF1={5};
		var _isFF15={6};
        var _isIE7={7}";

        // menu specific script-level variables
        private string _js_menuScriptLevelVariables = @"
		var _SCS_MM_activeContextMenuId=null;";

        private string _js_toolTipDisplay = @"
		function _SCS_MM_showTip(source,e,text)|-
			_SCS_MM_showToolTip(source,e,text,'{0}',{1},{2});
		-|
		function _SCS_MM_hideTip(source,e,text)|-
			_SCS_MM_hideToolTip('{3}')
		-|
		";

        // class swapping mouseover effects client script (first half)
        private static string _js_mouseoverEffects_1 = @"
		function _SCS_MM_~~MENU-ID~~_hlight(e,source,state,type){
			var classArray=new Array(";

        // class swapping mouseover effects client script (second half)
        private static string _js_mouseoverEffects_2 = @");
			_SCS_MM_setCls(e,source,state,classArray[type].split(':')[state]);}var _SCS_MM_manualToggle=false;";

        // image swapping mouseover effects client script
        private string _js_imageMouseoverEffects = @"
		function _SCS_MM_~~MENU-ID~~_imgSwap(e,menuItem,state){
			var id,index,type;
			var args = _SCS_MM_~~MENU-ID~~_imgSwap.arguments;
			for(i=3;i<args.length;i++){
				index=args[i];
				i++;
				type=args[i];
				if(index!=null){
					id = menuItem.id.replace('_itm_','_' + type + 'img_');	
					_SCS_MM_setImg(e,id,_SCS_MM_~~MENU-ID~~_imgs[index*2+state].src);}}}var _SCS_MM_manualToggle=false;";

        private string _js_imageMouseoverPreloading = @"
		var _SCS_MM_~~MENU-ID~~_imgs=new Array();

		function _SCS_MM_~~MENU-ID~~_preloadImages(){
			for (var i=0; i<_SCS_MM_~~MENU-ID~~_preloadImages.arguments.length; i++){
				_SCS_MM_~~MENU-ID~~_imgs[i] = new Image();
				_SCS_MM_~~MENU-ID~~_imgs[i].src = _SCS_MM_~~MENU-ID~~_preloadImages.arguments[i];
			}
		}
		_SCS_MM_~~MENU-ID~~_preloadImages(~~ARGS~~);";

        // context menu activation events
        private static string _js_contextMenuActivation = @"
		document.onmousedown=_SCS_MM_tglContextMnu;
		document.oncontextmenu=_SCS_MM_tglContextMnuStep2";

        // context menu activation events
        private static string _js_shortcutDetection = @"
		document.onkeydown=_SCS_onKeyDown;";

        // custom context menu client script
        // index 0 is id of menu, index 1-n are ids associated to index 0 menu
        private static string _js_contextMenuDetection = @"
		var _SCS_MM_contextIDs=_SCS_MM_discoverMenuData('_SCS_MM_contextIDs_');";

        // custom shortcut key client script
        // index 0 is id of menu, index 1 = (access key + access modifier + menu item id)
        private static string _js_shortcutKeyDetection = @"
		var _SCS_shortcutData=_SCS_MM_discoverMenuData('_SCS_shortcutData_');";

        #endregion

        #region Fields
        private MultiMenuGroup _topMenuGroup = new MultiMenuGroup();
        private MenuType _menuType = MenuType.Horizontal;
        private ContextHotSpotCollection _contextHotSpots = null;
        private GroupClassCollection _groupClasses = null;
        private ItemClassCollection _itemClasses = null;
        private MenuMarkerCollection _rightMenuMarkers = null;
        private MenuMarkerCollection _leftMenuMarkers = null;
        private StringCollection _classMouseovers = new StringCollection();
        private StringCollection _imageMouseovers = new StringCollection();
        private StringBuilder _shorcutData = new StringBuilder(30);
        private XPathDocument _xmlDocument = null;
        private string _xml = string.Empty;
        private string _xmlPath = string.Empty;
        private string _pixelUrl = string.Empty;
        private string _applicationUrlBase = string.Empty;
        private string _designTimeBasePath = string.Empty;
        private string _queryStringSelectedItemKey = string.Empty;
        private string _queryStringParamsToPass = string.Empty;
        private string _queryStringDataToAdd = string.Empty;
        private string _encryptionKey = string.Empty;
        private bool _enableMultiPageState = false;
        private bool _toolTipsSet = false;
        private bool _stylesheetWritten = false;
        private bool _encryptQueryString = false;
        private int _tabStop = -1;
        private MultiMenuItem _selectedMenuItem = null;
        private MultiMenuItem _lastSelectedMenuItem = null;
        
        private QueryString _passAlongQueryString = null;
        private QueryString _currentQueryString = null;

        private static string _imageResourcePrefix = "SCS.Web.UI.WebControls.Resources.Images";
        private static string _scriptResourcePrefix = "SCS.Web.UI.WebControls.Resources.ClientScripts";
        private static string _styleResourcePrefix = "SCS.Web.UI.WebControls.Resources.StyleSheets";
        private static string _stylesheetHtml = "<link rel=\"stylesheet\" type=\"text/css\" href=\"{0}\" />";

        internal string DesignDebugInfo = string.Empty;

        private UniqueIdType _idType = UniqueIdType.Id;

        #region Browser Detection
        private bool isInternetExplorerRequest = false;
        private bool isNetscapeRequest = false;
        private bool isFireFoxRequest = false;
        private bool isFireFox15Request = false;
        private bool isOperaRequest = false;
        private bool isLynxRequest = false;
        private bool isDomSupport = false;
        private bool isJavaScriptSupport = false;
        private int browserMajorVersion = 0;
        private double browserMinorVersion = 0;
        #endregion
        #endregion

        private enum ImageType { LeftIcon = 0, MiddleImage, RightIcon }
        private enum ResourceType { Image = 0, StyleSheet, PageLink, XmlData }

        #region Events
        private static readonly object menuItemClickedEvent = new object();
        public event ItemClickedHandler MenuItemClicked
        {
            add
            {
                Events.AddHandler(menuItemClickedEvent, value);
            }
            remove
            {
                Events.RemoveHandler(menuItemClickedEvent, value);
            }
        }

        private static readonly object xmlLoadedEvent = new object();
        public event EventHandler XmlLoaded
        {
            add
            {
                Events.AddHandler(xmlLoadedEvent, value);
            }
            remove
            {
                Events.RemoveHandler(xmlLoadedEvent, value);
            }
        }
        private void OnXmlLoaded(EventArgs e)
        {
            EventHandler eventDelegate = (EventHandler)Events[xmlLoadedEvent];

            if (eventDelegate != null)
            {
                eventDelegate(this, e);
            }
        }

        private static readonly object xmLoadingEvent = new object();
        public event EventHandler XmLoading
        {
            add
            {
                Events.AddHandler(xmLoadingEvent, value);
            }
            remove
            {
                Events.RemoveHandler(xmLoadingEvent, value);
            }
        }
        private void OnXmLoading(EventArgs e)
        {
            EventHandler eventDelegate = (EventHandler)Events[xmLoadingEvent];

            if (eventDelegate != null)
            {
                eventDelegate(this, e);
            }
        }

        public delegate void MenuItemCreatedHandler(object sender, MenuItemCreatedEventArgs e);
        private static readonly object menuItemCreatedEvent = new object();
        public event EventHandler ItemCreatedEvent
        {
            add
            {
                Events.AddHandler(menuItemCreatedEvent, value);
            }
            remove
            {
                Events.RemoveHandler(menuItemCreatedEvent, value);
            }
        }
        private void OnMenuItemCreated(MenuItemCreatedEventArgs e)
        {
            MenuItemCreatedHandler eventDelegate = (MenuItemCreatedHandler)Events[menuItemCreatedEvent];

            if (eventDelegate != null)
            {
                eventDelegate(this, e);
            }
        }

        private static readonly object menuGroupCreatedEvent = new object();
        public event EventHandler GroupCreatedEvent
        {
            add
            {
                Events.AddHandler(menuItemCreatedEvent, value);
            }
            remove
            {
                Events.RemoveHandler(menuItemCreatedEvent, value);
            }
        }
        private void OnMenuGroupCreated(MenuGroupCreatedEventArgs e)
        {
            MenuGroupCreatedHandler eventDelegate = (MenuGroupCreatedHandler)Events[menuGroupCreatedEvent];

            if (eventDelegate != null)
            {
                eventDelegate(this, e);
            }
        }

        #endregion

        public MultiMenu()
        {
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            // Note: this event is the only event called in design time; however, if the
            //      page has a master page attached, it will not be called.
            //

            if (!this.DesignMode)
            {         
                // set browser capability flags
                this.browserMajorVersion = Page.Request.Browser.MajorVersion;
                this.browserMinorVersion = Page.Request.Browser.MinorVersion;
                this.isDomSupport = (Page.Request.Browser.W3CDomVersion.Major >= 1);
                this.isJavaScriptSupport = (Page.Request.Browser.EcmaScriptVersion.Major > 0 && Page.Request.Browser.EcmaScriptVersion.Minor > 1) ? true : false;

                this.isNetscapeRequest = (Page.Request.Browser.Type.ToUpper().IndexOf("NETSCAPE") == -1) ? false : true;
                this.isInternetExplorerRequest = (Page.Request.Browser.Type.ToUpper().IndexOf("IE") > -1) ? true : false;
                this.isFireFoxRequest = (Page.Request.Browser.Type.ToUpper().IndexOf("FIREFOX") == -1) ? false : true;
                this.isFireFox15Request = (this.isFireFoxRequest && browserMajorVersion > 1 && browserMinorVersion > .4)
                    || (this.isFireFoxRequest && browserMajorVersion >= 2);
                this.isOperaRequest = (Page.Request.Browser.Type.ToUpper().IndexOf("OPERA") == -1) ? false : true;
                this.isLynxRequest = (Page.Request.Browser.Type.ToUpper().IndexOf("LYNX") == -1) ? false : true;

                this._js_commonScriptLevelVariables = string.Format(_js_commonScriptLevelVariables,
                    this.isInternetExplorerRequest && this.browserMajorVersion >= 5 && this.browserMajorVersion < 7,
                    this.isNetscapeRequest && this.browserMajorVersion <= 6,
                    this.isNetscapeRequest && this.browserMajorVersion >= 6,
                    this.isOperaRequest && this.browserMajorVersion >= 7,
                    this.isDomSupport,
                    this.isFireFoxRequest,
                    this.isFireFox15Request,
                    this.isInternetExplorerRequest && this.browserMajorVersion >= 7);

                this._js_commonScriptLevelVariables =
                    _js_commonScriptLevelVariables.Replace("True", "true").Replace("False", "false");

                if (!this.DesignMode && Page.Trace.IsEnabled)
                {
                    Page.Trace.Write("User Agent: " + Page.Request.UserAgent, "Browser Info");
                    Page.Trace.Write("Name: " + Page.Request.Browser.Browser, "Browser Info");
                    Page.Trace.Write("Type: " + Page.Request.Browser.Type, "Browser Info");
                    Page.Trace.Write("Netscape Request: " + isNetscapeRequest.ToString(), "Browser Info");
                    Page.Trace.Write("Netscape Request: " + isNetscapeRequest.ToString(), "Browser Info");
                    Page.Trace.Write("Internet Explorer Request: " + isInternetExplorerRequest.ToString(), "Browser Info");
                    Page.Trace.Write("FireFox Request: " + isFireFoxRequest.ToString(), "Browser Info");
                    Page.Trace.Write("Opera Request: " + isOperaRequest.ToString(), "Browser Info");
                    Page.Trace.Write("Lynx Request: " + isLynxRequest.ToString(), "Browser Info");
                    Page.Trace.Write("Major Version: " + browserMajorVersion.ToString(), "Browser Info");
                    Page.Trace.Write("Minor Version: " + browserMinorVersion.ToString(), "Browser Info");
                    Page.Trace.Write("DOM Version: " + Page.Request.Browser.W3CDomVersion.ToString(), "Browser Info");
                    Page.Trace.Write("ECMA Script Version: " + Page.Request.Browser.EcmaScriptVersion.ToString(), "Browser Info");
                    Page.Trace.Write("Table Support: " + Page.Request.Browser.Tables, "Browser Info");
                    Page.Trace.Write("JavaScript Support: " + isJavaScriptSupport, "Browser Info");
                }
            }
        }
        protected override void OnLoad(EventArgs e)
        {
            // Note: this event is not called in design time.
            //

            base.OnLoad(e);

            if (!this.DesignMode)
            {
                // deserialize xml into menu item objects
                this.LoadXml();

                // set the currently selected item from the id passed on the query string.
                //          
                _currentQueryString = new QueryString(Page.Request.QueryString.ToString(), _encryptionKey);

                // set selected menu item using menu id passed on query string
                string key = (string.IsNullOrEmpty(_queryStringSelectedItemKey)) ? "mmid" : _queryStringSelectedItemKey;

                if (_currentQueryString[key] != null)
                {
                    string id = _currentQueryString[key];
                    this.SetSelectedItem(this._topMenuGroup, -1, id);
                }
            }
        }
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (!this.DesignMode)
            {
#if DEBUG
                Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "_SCS_ScriptLibrary",
                    Page.ClientScript.GetWebResourceUrl(this.GetType(),
                        _scriptResourcePrefix + ".SCSScriptLibrary.js"));

                Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "_SCS_MM_ScriptLibrary",
                      Page.ClientScript.GetWebResourceUrl(this.GetType(),
                        _scriptResourcePrefix + ".MMScriptLibrary.js"));
#else
                Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "_SCS_ScriptLibrary",
                    Page.ClientScript.GetWebResourceUrl(this.GetType(),
                        _scriptResourcePrefix + ".SCSScriptLibrary.min.js"));

                Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "_SCS_MM_ScriptLibrary",
                      Page.ClientScript.GetWebResourceUrl(this.GetType(),
                        _scriptResourcePrefix + ".MMScriptLibrary.min.js"));
#endif
                if (this.isNetscapeRequest)
                    this._pixelUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(),
                            _imageResourcePrefix + ".pixel.gif");
            }

            // write style sheet reference           
            if (Page.Header != null)
            {
                string stylesheetPath = this.GetStylesheetUrl();

                Literal styleUrl = new Literal();
                styleUrl.Text = string.Format(_stylesheetHtml, stylesheetPath);
                Page.Header.Controls.Add(styleUrl);

                this._stylesheetWritten = true;
            }
        }
        protected override void Render(HtmlTextWriter writer)
        {
            base.Render(writer);
            this.WriteMenu(writer);
        }

        #region Rendering

        internal void WriteMenu(HtmlTextWriter writer)
        {
            if (this._topMenuGroup.MultiMenuItems.Count < 1)
                return;

            // initiate tab stop position
            this._tabStop = this.TabStartIndex;

            // set cell width
            // if menu is context or vertical, then the first group 
            // width should be the same width as the overall menu width
            if (this._menuType != MenuType.Horizontal)
            {
                if (this.Width > this._topMenuGroup.Width)
                    this._topMenuGroup.Width = this.Width;
                else
                    this.Width = this._topMenuGroup.Width;
            }

            // add mouseover effects to collection for later client script generation
            if (this._itemClasses != null)
                foreach (ItemClass itemClass in this._itemClasses)
                    this.RegisterMouseoverClass(itemClass.Normal, itemClass.Over);

            // add keyboard access key to first menu item
            this.RegisterShortcutKeys(this.UniqueID + "_itm_0", this.AccessKey, this.AccessKeyModifier);

            // write stylesheet if unable to add to the page header in preRender event           
            if (!this._stylesheetWritten)
            {
                string stylesheetPath = this.GetStylesheetUrl();

                if (string.IsNullOrEmpty(stylesheetPath)) 
                    // use default stylesheet if user has not specified one
                    stylesheetPath = Page.ClientScript.GetWebResourceUrl(this.GetType(),
                        _styleResourcePrefix + ".DefaultStyle.css");

                writer.Write(string.Format(_stylesheetHtml, stylesheetPath));
            }

            #region Write Menu Skip Link
            //
            // write skip link for page readers if text is set
            //

            if (this.EnableSkipLink)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Href, string.Format("#{0}_skip", this.UniqueID.Replace(":", "_")));
                writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, this.GetTabStop(null).ToString());
                writer.RenderBeginTag(HtmlTextWriterTag.A);
                this.WriteHiddenImage(writer, this.SkipLinkText, 1, 1);
                writer.RenderEndTag();
            }

            #endregion

            //
            // Write top level div group
            //

            // width to be used by div and table
            writer.AddAttribute(HtmlTextWriterAttribute.Id, this.UniqueID);

            if (this.EnableKeyboard)
                writer.AddAttribute("onkeydown", "_SCS_MM_onKeyPress(event, this)");

            this.WriteGroupStyleClass(writer, _topMenuGroup, 0);
           
            // write top group visibility and position
            string style = string.Empty;

            if (this._menuType != MenuType.Context)
                style = "position:relative;visibility:visible;top:0;left:0;";
            else
                style = "position:absolute;visibility:hidden;";

            #region Set Height and Width Css Style

            // write top group width
            string width = string.Empty;

            if (this.Width > -1)
                width = this.Width.ToString();

            else if (this._topMenuGroup.Width > -1)
                width = this._topMenuGroup.Width.ToString();

            if (width.Length > 0)
                style = string.Format("{0}width:{1}px;", style, width);
            else
                style = string.Format("{0}width:100%;", style);

            // write top group height
            string height = string.Empty;

            if (this.Height > -1)
                height = this.Height.ToString();

            if (height.Length > 0)
                style = string.Format("{0}height:{1}px;", style, height);

            if (style.Length > 0)
                writer.AddAttribute(HtmlTextWriterAttribute.Style, style);

            #endregion

            writer.RenderBeginTag(HtmlTextWriterTag.Div);
            // finish writing div tag <div ...>

            //
            // write top level table
            //
            if (width.Length > 0)
                writer.AddAttribute(HtmlTextWriterAttribute.Width, width);

            //if (height.Length > 0)
            //    writer.AddAttribute(HtmlTextWriterAttribute.Height, height);

            writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0");
            writer.RenderBeginTag(HtmlTextWriterTag.Table);

            // write row with no attributes only for horizontal menu
            if (this.MultiMenuType == MenuType.Horizontal)
                writer.RenderBeginTag(HtmlTextWriterTag.Tr);

            // write child elements
            this.WriteTopMenu(writer);

            // close row and table </tr></table>
            //
            if (this.MultiMenuType == MenuType.Horizontal)
                writer.RenderEndTag();

            // close top group and top table
            writer.RenderEndTag();
            writer.RenderEndTag();

            // write destination anchor for skip link 
            if (this.EnableSkipLink)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Name,
                    string.Format("{0}_skip", this.UniqueID.Replace(":", "_")));

                writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, this.GetTabStop(null).ToString());
                writer.RenderBeginTag(HtmlTextWriterTag.A);
                writer.RenderEndTag();
            }

            // write tool tip area
            //
            if (this._toolTipsSet)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Id, this.UniqueID + "_toolTip");
                writer.AddAttribute(HtmlTextWriterAttribute.Style, "z-index:99;visibility:hidden;position:absolute");
                writer.RenderBeginTag(HtmlTextWriterTag.Div);
                writer.RenderEndTag();
            }

            // write javascript startup scripts for control
            this.RegisterStartupScripts();
        }

        private void WriteTopMenu(HtmlTextWriter writer)
        {
            int level = 0;
            int leftIconGreatestWidth = 0;

            // set the menu marker objects for all menu items in this group to use           
            _topMenuGroup.LeftMenuMarkerForItemsToUse = this.GetLevelMenuMarker(_topMenuGroup, ImageType.LeftIcon, 0);
            _topMenuGroup.RightMenuMarkerForItemsToUse = this.GetLevelMenuMarker(_topMenuGroup, ImageType.RightIcon, 0);

            // detect if a left icon has been set and the max width of all left icons
            bool leftIconSet = this.IsLeftIconSet(this._topMenuGroup, 0, out leftIconGreatestWidth);
            bool lastItemWasDivider = false;

            // write menu items for menu group
            for (int i = 0; i < this._topMenuGroup.MultiMenuItems.Count; i++)
            {
                MultiMenuItem menuItem = this._topMenuGroup.MultiMenuItems[i];
                menuItem.TabIndex = this.GetTabStop(menuItem);

                // set items's parent group. this won't be set if item has
                // been created through the api
                if (menuItem.ParentMenuGroup == null)
                    menuItem.ParentMenuGroup = _topMenuGroup;

                // write row if rendering a vertical menu
                if (this.MultiMenuType != MenuType.Horizontal)
                    writer.RenderBeginTag(HtmlTextWriterTag.Tr);

                if (!menuItem.IsDivider)
                {
                    // get item and group ids
                    string[] ids = this.GetIds(menuItem, level, i);
                    menuItem.ID_internal = ids[0];

                    if (menuItem.SubMenuGroup != null)
                        menuItem.SubMenuGroup.ID_internal = ids[1];

                    if (menuItem.Visible)
                    {
                        lastItemWasDivider = false;

                        // write table cell and its contents
                        this.WriteItemCell(writer, menuItem, level);
                        this.WriteItemCellContents(writer, menuItem, leftIconSet, leftIconGreatestWidth, level);

                        if (!this.DesignMode
                            && menuItem.SubMenuGroup != null
                            && menuItem.Enabled
                            && menuItem.SubMenuGroup.MultiMenuItems.VisibleCount > 0)
                        {
                            // set group's parent item. this won't be set if group has
                            // been created through the api
                            if (menuItem.SubMenuGroup.ParentMenuItem == null)
                                menuItem.SubMenuGroup.ParentMenuItem = menuItem;

                            // build menu menuItem's children
                            this.WriteSubMenu(writer, menuItem.SubMenuGroup, level);
                        }

                        // close cell
                        writer.RenderEndTag();
                    }
                }
                else
                {	// write divider
                    if (menuItem.Visible)
                    {
                        // write divider, but only if the previous item wasn't a divider--we don't want dividers doubling-up
                        if (!lastItemWasDivider)
                            this.WriteDivider(writer, menuItem, level);

                        lastItemWasDivider = true;
                    }
                }

                // close row if rendering a vertical menu
                if (this.MultiMenuType != MenuType.Horizontal)
                    writer.RenderEndTag();
            }
        }
        private void WriteSubMenu(HtmlTextWriter writer, MultiMenuGroup menuGroup, int level)
        {
            level++;

            // set the menu marker objects for all menu items in this group to use           
            menuGroup.LeftMenuMarkerForItemsToUse = this.GetLevelMenuMarker(_topMenuGroup, ImageType.LeftIcon, level);
            menuGroup.RightMenuMarkerForItemsToUse = this.GetLevelMenuMarker(_topMenuGroup, ImageType.RightIcon, level);

            // write inner div tag	
            //
            writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "absolute");
            writer.AddStyleAttribute(HtmlTextWriterStyle.Visibility, "hidden");

            if (menuGroup.Width > -1)
                writer.AddStyleAttribute(HtmlTextWriterStyle.Width, menuGroup.Width.ToString() + "px");

            writer.AddAttribute(HtmlTextWriterAttribute.Id, menuGroup.Id);
            this.WriteGroupStyleClass(writer, menuGroup, level);
            writer.RenderBeginTag(HtmlTextWriterTag.Div);

            // write table
            //
            writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0");
            writer.RenderBeginTag(HtmlTextWriterTag.Table);

            int leftIconGreatestWidth = 0;
            bool leftIconSet = this.IsLeftIconSet(menuGroup, level, out leftIconGreatestWidth);
            bool lastItemWasDivider = false;

            for (int i = 0; i < menuGroup.MultiMenuItems.Count; i++)
            {
                MultiMenuItem menuItem = menuGroup.MultiMenuItems[i];
                menuItem.TabIndex = this.GetTabStop(menuItem);

                // set item's parent group. this won't be set if item has
                // been created through the api
                if (menuItem.ParentMenuGroup == null)
                    menuItem.ParentMenuGroup = menuGroup;

                // get item and group ids
                string[] ids = this.GetIds(menuItem, level, i);
                menuItem.ID_internal = ids[0];

                if (menuItem.Visible)
                {
                    lastItemWasDivider = false;

                    // default cell width to group width if individual menuItem width is not set
                    if (menuItem.Width < 0 && menuGroup.Width > -1)
                        menuItem.Width = menuGroup.Width;

                    if (menuItem.SubMenuGroup != null)
                        menuItem.SubMenuGroup.ID_internal = ids[1];

                    // write menu menuItem
                    writer.RenderBeginTag(HtmlTextWriterTag.Tr);

                    if (!menuItem.IsDivider)
                    {
                        // write table cell and its contents
                        this.WriteItemCell(writer, menuItem, level);
                        this.WriteItemCellContents(writer, menuItem, leftIconSet, leftIconGreatestWidth, level);

                        if (menuItem.SubMenuGroup != null && menuItem.Enabled && menuItem.SubMenuGroup.MultiMenuItems.VisibleCount > 0)
                        {
                            // set group's parent item. this won't be set if group has
                            // been created through the api
                            if (menuItem.SubMenuGroup.ParentMenuItem == null)
                                menuItem.SubMenuGroup.ParentMenuItem = menuItem;

                            // build menu menuItem's children by starting the proccess all over again
                            this.WriteSubMenu(writer, menuItem.SubMenuGroup, level);
                        }

                        // close closing table cell tag
                        writer.RenderEndTag();
                    }
                    else
                    {	// write divider
                        if (menuItem.Visible)
                        {
                            // write divider, but only if the previous item wasn't a divider--we don't want dividers doubling-up
                            if (!lastItemWasDivider)
                                this.WriteDivider(writer, menuItem, level);

                            lastItemWasDivider = true;
                        }
                    }

                    // close table row </tr>
                    writer.RenderEndTag();
                }
            }
            // close table and div tags </table></div>
            writer.RenderEndTag();
            writer.RenderEndTag();
        }

        private string BuildImageOverClientCall(MultiMenuItem menuItem, int level)
        {
            string mouseover = string.Empty;
            string paramList = string.Empty;

            // write client script to swap main image
            if (menuItem.ImageOver.Length > 0 && menuItem.Image != menuItem.ImageOver)
            {
                int imageOverId = this.RegisterMouseoverImage
                    (
                        (!menuItem.Selected) ? menuItem.Image : menuItem.ImageSelected,
                          menuItem.ImageOver
                    );

                if (imageOverId > -1)
                    paramList = string.Format("{0}{1},{2},", paramList, imageOverId, "''");
            }

            // write client script to swap left icons
            ImageMouseoverInfo mouseoverInfo = this.GetItemCurrentAndHoverLeftIcons(menuItem, level);

            if (!string.IsNullOrEmpty(mouseoverInfo.OverUrl) && !mouseoverInfo.Url.Equals(mouseoverInfo.OverUrl, StringComparison.OrdinalIgnoreCase))
            {
                int imageOverId = this.RegisterMouseoverImage(mouseoverInfo.Url, mouseoverInfo.OverUrl);
            
                if (imageOverId > -1)
                    paramList = string.Format("{0}{1},{2},", paramList, imageOverId, "'l'");
            }

            // write client script to swap right icons
            mouseoverInfo = this.GetItemCurrentAndHoverRightIcons(menuItem, level);

            if (!string.IsNullOrEmpty(mouseoverInfo.OverUrl) && !mouseoverInfo.Url.Equals(mouseoverInfo.OverUrl, StringComparison.OrdinalIgnoreCase))
            {
                int imageOverId = this.RegisterMouseoverImage(mouseoverInfo.Url, mouseoverInfo.OverUrl);
            
                if (imageOverId > -1)
                    paramList = string.Format("{0}{1},{2},", paramList, imageOverId, "'r'");
            }

            if (paramList.Length > 0)
            {
                paramList = paramList.Substring(0, paramList.Length - 1);
                mouseover = string.Format("_SCS_MM_{0}_imgSwap(event,this,1,{1});", CodeFriendlyMenuId(), paramList);
            }

            return mouseover;
        }
        
        private void WriteItemCell(HtmlTextWriter writer, MultiMenuItem menuItem, int level)
        {
            // write table cell attributes
            writer.AddAttribute(HtmlTextWriterAttribute.Id, menuItem.ID_internal);

            if (menuItem.Width > -1)
                writer.AddStyleAttribute(HtmlTextWriterStyle.Width, menuItem.Width.ToString() + "px");

            // set cell height
            if (menuItem.Height > -1)
                writer.AddStyleAttribute(HtmlTextWriterStyle.Height, menuItem.Height.ToString() + "px");

            // default cell width to group width if individual menuItem width is not set
            if (menuItem.Width < 0 && this._topMenuGroup.Width > -1)
                menuItem.Width = this._topMenuGroup.Width;

            // get current normal and mouseover classes for menu menuItem
            string[] classes = GetItemCurrentAndHoverClasses(menuItem, level);

            //write client javascript calls for mouseover and toggling menus
            this.WriteClientCalls(writer, menuItem, classes, level);

            // write class if set
            if (classes[0].Length > 0)
                writer.AddAttribute(HtmlTextWriterAttribute.Class, classes[0].ToString());

            // if item is disabled and a class has not been set, 
            // use normal class and change its color to gray
            if (!menuItem.Enabled && string.IsNullOrEmpty(classes[0]))
            {
                //string className = string.Empty;
                //ItemClass itemClass = this.GetLevelItemClass(level);
                //className = (itemClass != null && itemClass.Normal.Length > 0) ? itemClass.Normal : menuItem.CssClass;

                //writer.AddAttribute(HtmlTextWriterAttribute.Class, className);
                writer.AddAttribute(HtmlTextWriterAttribute.Style, "color:gray;");
            }

            // complete table cell
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
        }
        private void WriteClientCalls(HtmlTextWriter writer, MultiMenuItem menuItem, string[] classes, int level)
        {
            this.WriteItemClickCode(writer, menuItem);

            if (!menuItem.Enabled)
                return;

            string mouseover = string.Empty;
            string mouseout = string.Empty;

            // write client script to toggle menu groups
            if (menuItem.SubMenuGroup != null && menuItem.SubMenuGroup.MultiMenuItems.Count > 0)
            {
                mouseover = string.Format("_SCS_MM_tglMnu(this,true,{0},{1});",
                    menuItem.SubMenuGroup.VerticalOffset, menuItem.SubMenuGroup.HorizontalOffset);

                mouseout = mouseover.Replace("true", "false");
            }

            // write client script to swap images
            string imageMouseOver = this.BuildImageOverClientCall(menuItem, level);

            if (!imageMouseOver.Equals(string.Empty))
            {
                mouseover = string.Format("{0}{1}", mouseover, imageMouseOver);
                mouseout = string.Format("{0}{1}", mouseout, imageMouseOver.Replace("this,1", "this,0"));
            }

            // write client script to display tool tips
            if (this.EnableToolTips && menuItem.ToolTip.Length > 0)
            {
                mouseover = string.Format("{0}_SCS_MM_showTip(this, event, '{1}');", mouseover, menuItem.ToolTip.Replace("'", "&#39;"), this.ToolTipDelay);
                mouseout = string.Format("{0}_SCS_MM_hideTip();", mouseout);

                this._toolTipsSet = true;
            }

            // write client script to swap css classes.  mouseovers are applied to items where 
            // 1) the mouseover class has been set
            // 2) the mouseover class differs from the normal class

            string itemClass = classes[0];
            string itemClassOver = classes[1];

            if (itemClassOver.Length > 0 && itemClass != itemClassOver)
            {
                // add mouseover effects to collection for later client script generation
                int mouseoverId = this.RegisterMouseoverClass(itemClass, itemClassOver);

                if (mouseoverId > -1)
                {
                    mouseover = string.Format("{0}_SCS_MM_{2}_hlight(event,this,1,{1});", mouseover, mouseoverId, CodeFriendlyMenuId());
                    mouseout = string.Format("{0}_SCS_MM_{2}_hlight(event,this,0,{1});", mouseout, mouseoverId, CodeFriendlyMenuId());
                }
            }

            // remove semicolon if necessary
            if (mouseover.Length > 0 && mouseover.Substring(0, 1).Equals(";"))
                mouseover = mouseover.Substring(1);

            if (mouseout.Length > 0 && mouseout.Substring(0, 1).Equals(";"))
                mouseout = mouseout.Substring(1);

            if (mouseover.Length > 0)
            {
                writer.AddAttribute("onmouseover", "try{" + mouseover + "}catch(e){}");
                writer.AddAttribute("onmouseout", "try{" + mouseout + "}catch(e){}");
            }

            // register shortcut key data to be writtein to hidden text field later
            if (!menuItem.AccessKey.Equals(AccessKeyType.None))
            {
                this.RegisterShortcutKeys(menuItem.Id, menuItem.AccessKey, menuItem.AccessKeyModifier);
            }
        }
        private void WriteItemClickCode(HtmlTextWriter writer, MultiMenuItem menuItem)
        {
            string onClickCode = string.Empty;

            if (menuItem.Enabled)
            {
                if (string.IsNullOrEmpty(menuItem.OnClickClientCode))
                {
                    // custom client code has not been set
                    // write post-back reference or script to follow hyperlink
                    if (menuItem.PostBack)
                    {
                        onClickCode = Page.ClientScript.GetPostBackEventReference(this, menuItem.ID_internal) + ";";
                        onClickCode += "_SCS_cancelEventBubble(event);";
                    }
                    else if (!string.IsNullOrEmpty(menuItem.Url))
                    {
                        onClickCode = "_SCS_MM_execChildUrl(this, event);";
                    }

                    //if (!string.IsNullOrEmpty(onClickCode))
                    //    onClickCode += "_SCS_cancelEventBubble(event);";
                }
                else
                {
                    onClickCode = menuItem.OnClickClientCode;

                    // context menus should close after their client-side code has executed
                    // append function to close menu
                    if (this.MultiMenuType == MenuType.Context)
                    {
                        if (!onClickCode.EndsWith(";"))
                            onClickCode += ";";

                        onClickCode += "_SCS_MM_closeContextMenus(event);";
                    }
                }
            }
            else
            {
                onClickCode = "_SCS_cancelEventBubble(event);";
            }

            if (onClickCode.Length > 0)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Onclick, onClickCode);
            }
        }
        private void WriteItemCellContents(HtmlTextWriter writer, MultiMenuItem menuItem, bool leftIconSet, int leftIconGreatestWidth, int level)
        {
            // netscape and top level item icons are not written with absolute positioning, 
            // they are inserted into the normal control flow instead.
            if (this.isNetscapeRequest || (level == 0 && _menuType == MenuType.Horizontal))
            {
                if (this.isNetscapeRequest && leftIconSet && menuItem.LeftIcon.Length == 0)
                {
                    // create indentation for netscape items using a hidden image
                    this.WriteHiddenImage(writer, string.Empty, 0, leftIconGreatestWidth);
                }
                else
                {
                    this.WriteItemImage(writer, menuItem, ImageType.LeftIcon, level);
                }
            }

            bool isMultiWord = (menuItem.Text.IndexOf(" ") > -1 || menuItem.Text.IndexOf("&nbsp;") > -1);

            //
            // write real url if hyperlinks are enabled, otherwise write code to prevent navigation
            //

            string url = string.Empty;

            if (!menuItem.PostBack && !string.IsNullOrEmpty(menuItem.Url))
            {
                url = menuItem.Url;

                #region Build-Out QueryString

                string qs = "";

                int index = url.IndexOf("?");

                if (index != -1)
                {
                    qs = url.Substring(index + 1);
                    url = url.Substring(0, index);
                }

                QueryString originalQueryString = new QueryString(_encryptionKey);                
                originalQueryString.Parse(qs);

                string queryStringAppend = string.Empty;

                if (!this.DesignMode)
                {
                    //
                    // add id to query string to persist selection between pages (non-postbacks).
                    //

                    if (_enableMultiPageState)
                    {
                        originalQueryString.Add("sender", this.UniqueID);
                        
                        string key = (string.IsNullOrEmpty(_queryStringSelectedItemKey)) ? "mmid" : _queryStringSelectedItemKey;
                        string val = (_idType.Equals(UniqueIdType.Id)) ? menuItem.Id : menuItem.Tag;

                        originalQueryString.Add(key, val);
                    }

                    //
                    // add any desired previous query string values to this url's query string
                    //

                    if (_passAlongQueryString == null && !string.IsNullOrEmpty(_queryStringParamsToPass))
                    {   
                        string[] paramKeys = _queryStringParamsToPass.Split(',', ';', '|');                        
                        
                        _passAlongQueryString = new QueryString();
                        _passAlongQueryString.EncryptionKey = _encryptionKey;

                        for (int i = 0; i < paramKeys.Length; i++)
                        {
                            if (!string.IsNullOrEmpty(_currentQueryString[paramKeys[i]]))
                                _passAlongQueryString.Add(paramKeys[i], _currentQueryString[paramKeys[i]]);
                        }
                    }

                    // original params trump pass along params
                    if (_passAlongQueryString != null)
                        originalQueryString.Merge(_passAlongQueryString, QueryString.DuplicateActionType.RemoveSource);

                    //
                    // add partial query string data if set
                    //

                    if (!string.IsNullOrEmpty(_queryStringDataToAdd))
                    {
                        QueryString _appendQueryString = new QueryString(_encryptionKey);
                        _appendQueryString.Parse(_queryStringDataToAdd);

                        // append params trump pass all other params
                        originalQueryString.Merge(_appendQueryString, QueryString.DuplicateActionType.RemoveTarget);
                    }
                }

                // encrypt query string if flag and key have been set
                url += originalQueryString.ToString((_encryptQueryString && !string.IsNullOrEmpty(_encryptionKey)), true);

                #endregion

                /*if (this.Target.Length > 0)
                    writer.AddAttribute(HtmlTextWriterAttribute.Target, this.Target);*/

                url = this.GetResolvedPath(url, ResourceType.PageLink, false);
            }

            writer.AddAttribute(HtmlTextWriterAttribute.Href, (url.Length > 0) ? url : "javascript:void(0);");
            if (!string.IsNullOrEmpty(menuItem.UrlTarget))
              writer.AddAttribute(HtmlTextWriterAttribute.Target, menuItem.UrlTarget);

            //writer.AddAttribute(HtmlTextWriterAttribute.Onclick, "_SCS_cancelEventBubble(event);");

            //if (!string.IsNullOrEmpty(url))
            //    writer.AddAttribute(HtmlTextWriterAttribute.Href, url, true);

            // set tabindex
            if (this.EnableKeyboard) // && menuItem.TabIndex > -1)
                writer.AddAttribute(HtmlTextWriterAttribute.Tabindex,
                    menuItem.TabIndex.ToString());

            if (leftIconSet && !(this.isNetscapeRequest || (level == 0 && _menuType == MenuType.Horizontal)))
                writer.AddAttribute(HtmlTextWriterAttribute.Style,
                    string.Format("position:relative; left:{0}px", leftIconGreatestWidth));

            // when tabbing on to first menu item, trigger mouseover event
            if (menuItem.Id.EndsWith("_itm_0"))
                writer.AddAttribute("onfocus", "_SCS_MM_handleTabOn(this)");

            // write access key if alt modifier is specified
            if (menuItem.AccessKey != AccessKeyType.None && menuItem.AccessKeyModifier == AccessKeyModifierType.Alt)
                writer.AddAttribute(HtmlTextWriterAttribute.Accesskey,
                    Enum.GetName(typeof(AccessKeyType), menuItem.AccessKey));

            // special case for lynx browser. do not display links where there is no href specified
            if (this.isLynxRequest && url.Length == 0)
            {
                writer.RenderBeginTag(HtmlTextWriterTag.Span);
            }
            else
            {
                writer.RenderBeginTag(HtmlTextWriterTag.A);
            }

            // enclose cell's content in nobr tags if no wrapping is set 
            // and text is more than one word
            if (!this.WordWrap && isMultiWord)
                writer.RenderBeginTag(HtmlTextWriterTag.Nobr);

            // image trumps text. text will be used for altText if imageAltText is not set
            if (menuItem.Image.Length == 0)
                writer.Write(menuItem.Text);

            // close nobr tag if no wrapping is set
            if (!this.WordWrap && isMultiWord)
            {
                writer.RenderEndTag();
            }

            // write center image
            this.WriteItemImage(writer, menuItem, ImageType.MiddleImage, level);

            // close anchor tag
            writer.RenderEndTag();

            // write left, right, and center images if  set
            //
            // netscape and top level item icons are not to be written with absolute positioning, 
            // they are inserted into the normal control flow instead.
            //
            if (!(this.isNetscapeRequest || (level == 0 && _menuType == MenuType.Horizontal)))
                this.WriteItemImage(writer, menuItem, ImageType.LeftIcon, level);

            this.WriteItemImage(writer, menuItem, ImageType.RightIcon, level);
        }

        private void WriteDivider(HtmlTextWriter writer, MultiMenuItem menuItem, int level)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.Class, menuItem.CssClass);
            writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, "-1");            
            writer.RenderBeginTag(HtmlTextWriterTag.Td);

            this.WriteItemImage(writer, menuItem, ImageType.MiddleImage, level);

            // close cell
            writer.RenderEndTag();
        }
        private void WriteGroupStyleClass(HtmlTextWriter writer, MultiMenuGroup group, int level)
        {
            string className = string.Empty;

            if (group.CssClass.Length > 0)
            {
                className = group.CssClass;
            }
            else
            {
                GroupClass groupClass = GetLevelGroupClass(level);

                if (groupClass != null && groupClass.Normal.Length > 0)
                {
                    className = groupClass.Normal;
                }
                else if (string.IsNullOrEmpty(this.StyleSheetUrl))
                {
                    className = (level > 0) ? "SubMenuGroup" : "TopMenuGroup";
                }
            }

            writer.AddAttribute(HtmlTextWriterAttribute.Class, className);
        }

        private void WriteItemImage(HtmlTextWriter writer, MultiMenuItem menuItem, ImageType imageType, int level)
        {
            string id = string.Empty;
            string filePath = string.Empty;
            string altText = string.Empty;
            string style = string.Empty;

            int height = -1;
            int width = -1;

            bool useMarker = false;
            bool hasMouseOver = false;
            bool hasSubgroup = (menuItem.SubMenuGroup != null && menuItem.SubMenuGroup.MultiMenuItems.Count > 0) ? true : false;
            
            ImageMouseoverInfo marker;
            marker.Height = height;
            marker.Width = width;
            marker.OverUrl = "";
            marker.Url = "";

            switch (imageType)
            {
                case ImageType.RightIcon:
                    
                    filePath = menuItem.RightIcon;
                    id = menuItem.Id.Replace("_itm_", "_rimg_");
                    
                    if (filePath.Length == 0)
                    {
                        // an image has not been specifically set. do we need to display a menu marker?
                        if (menuItem.SubMenuGroup != null 
                            && menuItem.SubMenuGroup.MultiMenuItems.Count > 0
                            && (this.EnableTopMenuMarkers || level > 0)
                            && menuItem.SubMenuGroup.ExpandDirection == ExpandDirectionType.Right)
                        {
                            marker = this.GetItemCurrentAndHoverRightIcons(menuItem, level);
                            useMarker = true;
                            filePath = marker.Url;
                        }
                        else
                        {
                            // no image needs to be written, therefore exit
                            return;
                        }
                    }

                    height = (useMarker) ? marker.Height : menuItem.RightIconHeight;
                    width = (useMarker) ? marker.Width : menuItem.RightIconWidth;
                    altText = (useMarker) ? "" : menuItem.RightIconAltText;
                    hasMouseOver = (useMarker || menuItem.RightIconOver.Length > 0) ? true : false;


                    if (!(this.isNetscapeRequest || (level == 0 && this._menuType == MenuType.Horizontal)))
                    {
                        style = "position:absolute;right:0px";
                    }
                    break;

                case ImageType.LeftIcon:

                    filePath = menuItem.LeftIcon;
                    id = menuItem.Id.Replace("_itm_", "_limg_");

                    if (filePath.Length == 0)
                    {
                        // an image has not been specifically set. do we need to display a menu marker?
                        if (menuItem.SubMenuGroup != null 
                            && menuItem.SubMenuGroup.MultiMenuItems.Count > 0
                            && (this.EnableTopMenuMarkers || level > 0)
                            && menuItem.SubMenuGroup.ExpandDirection == ExpandDirectionType.Left)
                        {
                            marker = this.GetItemCurrentAndHoverLeftIcons(menuItem, level);
                            useMarker = true;
                            filePath = marker.Url;
                        }
                        else
                        {
                            // no image needs to be written, therefore exit
                            return;
                        }
                    }

                    height = (useMarker) ? marker.Height : menuItem.LeftIconHeight;
                    width = (useMarker) ? marker.Width : menuItem.LeftIconWidth;
                    altText = (useMarker) ? "" : menuItem.LeftIconAltText;
                    hasMouseOver = (useMarker || menuItem.LeftIconOver.Length > 0) ? true : false;

                    if (!(this.isNetscapeRequest || (level == 0 && this._menuType == MenuType.Horizontal)))
                    {
                        style = "position:absolute;left:0px";
                    }
                    break;

                case ImageType.MiddleImage:

                    filePath = (menuItem.Selected) ? menuItem.ImageSelected : menuItem.Image;
                    height = menuItem.ImageHeight;
                    width = menuItem.ImageWidth;

                    if (menuItem.IsDivider)
                    {
                        if (filePath.Length == 0)
                        {                          
                            // only top menu group dividers on the horizontal type menu 
                            // have vertical divider images.
                            if (level > 0 || !_menuType.Equals(MenuType.Horizontal))
                            {
                                filePath = ".divider_h.gif";
                            }
                            else
                            {
                                filePath = ".divider_v.gif";
                            }

                            // fill horizontal dividers to fill entire width of its cell
                            if (level > 0 || !_menuType.Equals(MenuType.Horizontal) && width < 0)
                            {
                                width = (menuItem.Width > -1) ? menuItem.Width : menuItem.ParentMenuGroup.Width;
                                height = 5;
                            }
                        }
                    }
                    else
                    {
                        if (menuItem.ImageOver.Length > 0)
                            hasMouseOver = true;

                        id = menuItem.Id.Replace("_itm_", "_img_");

                        if (!(menuItem.ToolTip.Length > 0 && this.EnableToolTips))
                        {
                            if (menuItem.ImageAltText.Length > 0)
                            {
                                altText = menuItem.ImageAltText;
                            }
                            else if (menuItem.Text.Length > 0)
                            {
                                altText = menuItem.Text;
                            }
                        }
                    }
                    break;
            }

            if (!string.IsNullOrEmpty(filePath))
            {
                filePath = this.GetResolvedPath(filePath, ResourceType.Image, false);
                
                writer.AddAttribute("src", filePath, (!this.DesignMode)); // do not use HtmlTextWriterAttribute for this. design time path will be encoded despite setting it not to.
                writer.AddAttribute(HtmlTextWriterAttribute.Border, "0");

                if (id.Length > 0 && hasMouseOver)
                    writer.AddAttribute(HtmlTextWriterAttribute.Id, id);

                if (height > -1)
                    writer.AddAttribute(HtmlTextWriterAttribute.Height, height.ToString());

                if (width > -1)
                    writer.AddAttribute(HtmlTextWriterAttribute.Width, width.ToString());

                if (style.Length > 0)
                    writer.AddAttribute(HtmlTextWriterAttribute.Style, style);

                if (this.EnableToolTips && altText.Trim().Length > 0)
                    writer.AddAttribute(HtmlTextWriterAttribute.Alt, altText);
                else if (menuItem.IsDivider)
                    writer.AddAttribute(HtmlTextWriterAttribute.Alt, string.Empty);

                writer.RenderBeginTag(HtmlTextWriterTag.Img);
                writer.RenderEndTag();
            }
        }
        private void WriteHiddenImage(HtmlTextWriter writer, string alternateText, int height, int width)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.Border, "0");
            writer.AddAttribute(HtmlTextWriterAttribute.Height, height.ToString());
            writer.AddAttribute(HtmlTextWriterAttribute.Width, width.ToString());
            writer.AddAttribute(HtmlTextWriterAttribute.Src, this._pixelUrl);

            if (alternateText.Length > 0)
                writer.AddAttribute(HtmlTextWriterAttribute.Alt, alternateText);

            writer.RenderBeginTag(HtmlTextWriterTag.Img);
            writer.RenderEndTag();
        }

        private void RegisterStartupScripts()
        {
            //
            // write client script references
            //
            // register script-level variables common to all SCS controls
            Page.ClientScript.RegisterStartupScript(typeof(string), "_SCS_CommonVariables", this.FormatScriptBlock(_js_commonScriptLevelVariables));

            if (this._shorcutData.Length > 0)
            {
                Page.ClientScript.RegisterHiddenField("_SCS_shortcutData_" + CodeFriendlyMenuId(), this._shorcutData.ToString());
                Page.ClientScript.RegisterStartupScript(typeof(string), "_SCS_ShortcutKeyDetection", this.FormatScriptBlock(_js_shortcutKeyDetection));
            }

            // register menu specific script-level variables
            Page.ClientScript.RegisterStartupScript(typeof(string), "_SCS_MM_CommonVariables",
                this.FormatScriptBlock(FormatClientCode(_js_menuScriptLevelVariables)));

            string letter = string.Empty;
            string modifier = string.Empty;
            string js = string.Empty;

            // turn off access keys if keyboard is not enabled 
            // by setting letter and modifier to empty strings
            if (this.EnableKeyboard)
            {
                letter = Enum.GetName(typeof(AccessKeyType), this.AccessKey);
                modifier = Enum.GetName(typeof(AccessKeyModifierType), this.AccessKeyModifier);

                Page.ClientScript.RegisterStartupScript(typeof(string), "_SCS_ShortcutDetection", this.FormatScriptBlock(_js_shortcutDetection));
            }

            // generate client script to display mouseovers
            if (this._classMouseovers.Count > 0)
            {
                string arrayData = string.Empty;

                // build array data to pass to javascript Array() constructor
                for (int i = 0; i < this._classMouseovers.Count; i++)
                    arrayData += string.Format("'{0}',", this._classMouseovers[i]);

                // remove last comma
                arrayData = arrayData.Substring(0, arrayData.Length - 1);

                // register mouseover script with page
                js = this.FormatScriptBlock(_js_mouseoverEffects_1 + arrayData + _js_mouseoverEffects_2);
                js = js.Replace("~~MENU-ID~~", CodeFriendlyMenuId());

                Page.ClientScript.RegisterStartupScript(typeof(string),
                    string.Format("_SCS_MM_{0}_MouseoverEffects", CodeFriendlyMenuId()), js);
            }

            // write image mouse over code effects
            if (this._imageMouseovers.Count > 0)
            {
                string arguments = string.Empty;

                // build argument list of image urls for client side image preloading function
                for (int i = 0; i < this._imageMouseovers.Count; i++)
                {
                    string[] images = this._imageMouseovers[i].Split(char.Parse(":"));

                    for (int j = 0; j < images.Length; j++)
                        images[j] = this.GetResolvedPath(images[j], ResourceType.Image, false);

                    arguments += string.Format("'{0}','{1}',", images[0], images[1]);
                }

                // remove last comma
                arguments = arguments.Substring(0, arguments.Length - 1);

                // register mouseover script with page
                js = this._js_imageMouseoverEffects.Replace("~~MENU-ID~~", CodeFriendlyMenuId());
                js += this._js_imageMouseoverPreloading.Replace("~~MENU-ID~~", CodeFriendlyMenuId()).Replace("~~ARGS~~", arguments);

                Page.ClientScript.RegisterStartupScript(typeof(string), string.Format("_SCS_MM_{0}_ImageEffects",
                    CodeFriendlyMenuId()), this.FormatScriptBlock(js));
            }

            // write client code specific to context menus
            if (this._menuType == MenuType.Context)
            {
                // register context menu scripts with page
                Page.ClientScript.RegisterStartupScript(typeof(string), "_SCS_MM_MouseDownTrap",
                    this.FormatScriptBlock(_js_contextMenuActivation));

                string values = this.UniqueID;

                foreach (ContextHotSpot hotSpots in this.ContextHotSpots)
                    values += string.Format(",{0}+{1}", hotSpots.ClientId, hotSpots.ChildDepth);

                // register hidden fields with page for context menu triggering
                Page.ClientScript.RegisterHiddenField("_SCS_MM_contextIDs_" + this.UniqueID, values);
                Page.ClientScript.RegisterStartupScript(typeof(string), "_SCS_MM_ContextMenuDetection", this.FormatScriptBlock(_js_contextMenuDetection));
            }

            // write client code specific to tool tips
            if (this._toolTipsSet && this.EnableToolTips)
            {
                string clientId = this.UniqueID + "_toolTip";
                string code = string.Format(_js_toolTipDisplay,
                    clientId, this.ToolTipDelay, this.ToolTipDuration, clientId);

                Page.ClientScript.RegisterStartupScript(typeof(string), "_SCS_MM_ToolTipDisplay", this.FormatScriptBlock(code));
            }
        }        
        private void RegisterShortcutKeys(string id, AccessKeyType letter, AccessKeyModifierType modifier)
        {
            // 0 = client id, 
            // 1 = letter, 
            // 2 = modifier, 
            // 3 = shortcut type, 
            // 4 = control type 
            if (_shorcutData.Length > 0)
                this._shorcutData.Append(",");

            this._shorcutData.Append(string.Format("{0}:{1}:{2}:F:MM", id, Enum.GetName(typeof(AccessKeyType), letter), (int)modifier));
        }

        private int RegisterMouseoverClass(string classNormal, string classMouseOver)
        {
            int position = -1;

            // to add mouseover effect, at least one class must be set
            // and classes cannot be the same (they would just canel themselves out)
            if ((classNormal != classMouseOver) && (classNormal.Length > 0 || classMouseOver.Length > 0))
            {
                string classes = string.Format("{0}:{1}", classNormal, classMouseOver);

                if (!this._classMouseovers.Contains(classes))
                    this._classMouseovers.Add(classes);

                // get position of classes to use later as parameter in client-side mouseover function
                position = this._classMouseovers.IndexOf(classes);
            }
            return position;
        }
        private int RegisterMouseoverImage(string imageNormal, string imageMouseOver)
        {
            int position = -1;

            // to add mouseover effect, at least one image must be set
            // and images cannot be the same (they would just canel themselves out)
            if ((imageNormal != imageMouseOver) && (imageNormal.Length > 0 || imageMouseOver.Length > 0))
            {
                string images = string.Format("{0}:{1}", imageNormal, imageMouseOver);

                if (!this._imageMouseovers.Contains(images))
                    this._imageMouseovers.Add(images);

                // get position of images to use later as parameter in client-side mouseover function
                position = this._imageMouseovers.IndexOf(images);
            }
            return position;
        }

        private bool IsLeftIconSet(MultiMenuGroup menuGroup, int level, out int leftIconGreatestWidth)
        {
            leftIconGreatestWidth = 0;
            bool imageSet = false;
        
            for (int i = 0; i < menuGroup.MultiMenuItems.Count; i++)
            {
                // icon defined with menu item takes precidence over level menu markers
                if (menuGroup.MultiMenuItems[i].LeftIcon.Length > 0
                    || (menuGroup.LeftMenuMarkerForItemsToUse != null
                        && menuGroup.MultiMenuItems[i].SubMenuGroup != null
                        && menuGroup.MultiMenuItems[i].SubMenuGroup.ExpandDirection == ExpandDirectionType.Left
                        && menuGroup.LeftMenuMarkerForItemsToUse.Url.Length > 0))
                {
                    int imageWidth = (menuGroup.MultiMenuItems[i].LeftIcon.Length > 0)
                        ? menuGroup.MultiMenuItems[i].LeftIconWidth : menuGroup.LeftMenuMarkerForItemsToUse.Width;

                    if (imageWidth > leftIconGreatestWidth)
                        leftIconGreatestWidth = imageWidth;

                    imageSet = true;
                }
            }

            if (leftIconGreatestWidth == 0)
                leftIconGreatestWidth = 15;

            return imageSet;
        }

        private string FormatScriptBlock(string code)
        {
            string scriptTags = "<script language=javascript type=\"text/javascript\">{0}</script>";
            return string.Format(scriptTags, this.FormatClientCode(code));
        }
        private string FormatScriptBlock(string code, string source)
        {
            string scriptTags = "<script language=javascript type=\"text/javascript\" src=\"{0}\">{1}</script>";
            return string.Format(scriptTags, source, this.FormatClientCode(code));
        }
        private string FormatScriptBlock(string code, string forElement, string eventName)
        {
            string scriptTags = "<script language=javascript type=\"text/javascript\" for={0} event={1}>{2}</script>";
            return string.Format(scriptTags, forElement, eventName, this.FormatClientCode(code));
        }
        
        internal string FormatClientCode(string text)
        {
            return text.Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace("-|", "}").Replace("|-", "{");
        }
        
        private string CodeFriendlyMenuId()
        {
            return this.UniqueID.Replace(":", "_");
        }

        #endregion

        #region Setter Methods

        public void SelectMenuItem(string tagOrId)
        {
            this.SetSelectedItem(_topMenuGroup, -1, tagOrId);
        }
        private void SetSelectedItem(MultiMenuGroup menuGroup, int level, string tagOrId)
        {
            level++;
            MultiMenuItem menuItem = null;

            bool matchFound = false;
            MultiMenuItem lastSelectedMenuItem = null;
            MultiMenuItem selectedMenuItem = null;

            for (int i = 0; i < menuGroup.MultiMenuItems.Count; i++)
            {
                menuItem = menuGroup.MultiMenuItems[i];

                if (level == 0)
                    menuItem.ID_internal = this.UniqueID + "_itm_" + i.ToString();
                else
                    menuItem.ID_internal = menuGroup.ParentMenuItem.ID_internal + "_" + i.ToString();

                if (tagOrId != menuItem.Id && tagOrId != menuItem.Tag)
                {
                    if (menuItem.Selected)
                    {
                        menuItem.Selected = false;
                        lastSelectedMenuItem = menuItem;
                    }
                }
                else
                {
                    matchFound = true;
                    selectedMenuItem = menuItem;
                }

                if (menuItem.SubMenuGroup != null && menuItem.SubMenuGroup.MultiMenuItems.Count > 0)
                    this.SetSelectedItem(menuItem.SubMenuGroup, level, tagOrId); // search deeper
            }

            // complete transaction
            //

            if (matchFound)
            {
                if (lastSelectedMenuItem != null)
                {
                    string replaceText = this.CurrentItemText.Replace("{0}", "");

                    if (replaceText.Length > 0)
                        lastSelectedMenuItem.Text = lastSelectedMenuItem.Text.Replace(replaceText, "");

                    this._lastSelectedMenuItem = lastSelectedMenuItem;
                }

                if (selectedMenuItem != null)
                {
                    selectedMenuItem.Selected = true;
                    selectedMenuItem.Text = string.Format(this.CurrentItemText, selectedMenuItem.Text);

                    if (this.SelectionHighlight == SelectionHighlightType.ItemPlusAncestors)
                        this.SelectItemAncestors(selectedMenuItem);

                    this._selectedMenuItem = selectedMenuItem;
                }
            }

            // determine if we should raise the clicked event for cross-page menuss.
            //

            if (this.EnableMultiPageState)
            {
                // having the last selected item in the event args for cross-page state 
                // is not supported, so set it to null
                this._lastSelectedMenuItem = null;

                if (!string.IsNullOrEmpty(_currentQueryString["sender"]))
                {
                    tagOrId = _currentQueryString["sender"];

                    if (!tagOrId.Equals(this.UniqueID, StringComparison.CurrentCulture))
                    {
                        // sender is not this control, so there is no need to continue.
                        return;
                    }
                }
                else
                {
                    // sender has not been passed, so there is no need to continue.
                    return;
                }
            }

            // raise clicked event
            //

            if (this._selectedMenuItem != null)
            {
                MenuItemClickedEventArgs eventArgs =
                    new MenuItemClickedEventArgs(_selectedMenuItem, _lastSelectedMenuItem);

                OnMenuItemClicked(eventArgs);

                if (eventArgs.Cancel)
                {
                    this._selectedMenuItem.Selected = false;

                    if (this._lastSelectedMenuItem != null)
                        this._lastSelectedMenuItem.Selected = true;

                    this._selectedMenuItem = this._lastSelectedMenuItem;

                    if (this.SelectionHighlight == SelectionHighlightType.ItemPlusAncestors)
                        this.SelectItemAncestors(this._selectedMenuItem);
                }
            }
        }
        private void SelectItemAncestors(MultiMenuItem menuItem)
        {
            MultiMenuItem parentMenuItem = menuItem.ParentMenuGroup.ParentMenuItem;

            if (parentMenuItem != null)
            {
                parentMenuItem.Selected = true;
                SelectItemAncestors(parentMenuItem);
            }
        }
     
        #endregion

        #region Getter Methods

        private MenuMarker GetLevelMenuMarker(MultiMenuGroup menuGroup, ImageType icon, int level)
        {
            if (menuGroup != null)
            {
                MenuMarkerCollection menuMarkers
                    = (icon == ImageType.RightIcon) ? this._rightMenuMarkers : this._leftMenuMarkers;

                if (menuMarkers != null)
                {
                    for (int i = menuMarkers.Count - 1; i > -1; i--)
                    {
                        if (level >= i)
                            return menuMarkers[i];
                    }
                }
            }
            // return default menu marker
            MenuMarker marker = new MenuMarker();

            string fileName = "";

            if (level == 0 && _menuType == MenuType.Horizontal)
            {
                fileName = "arrow_d";
            }
            else
            {
                fileName = (icon == ImageType.RightIcon) ? "arrow_r" : "arrow_l";
            }

            marker.Url = "." + fileName + "_n.gif";
            marker.OverUrl = "." + fileName + "_o.gif";
            marker.DisabledUrl = "." + fileName + "_d.gif";

            marker.SelectedUrl = marker.Url;
            marker.SelectedOverUrl = marker.SelectedOverUrl;
            
            marker.Height = 13;
            marker.Width = 15;

            return marker;
        }
        private GroupClass GetLevelGroupClass(int level)
        {
            if (_groupClasses != null)
            {
                for (int i = this._groupClasses.Count - 1; i > -1; i--)
                {
                    if (level >= i)
                        return this._groupClasses[i];
                }
            }
            return null;
        }
        private ItemClass GetLevelItemClass(int level)
        {
            if (_itemClasses != null)
            {
                for (int i = this._itemClasses.Count - 1; i > -1; i--)
                {
                    if (level >= i)
                        return this._itemClasses[i];
                }
            }
            return null;
        }

        private string[] GetItemCurrentAndHoverClasses(MultiMenuItem menuItem, int level)
        {
            // check menuItem for css classes first
            // if class it not set on the menuItem level, check the default menu property
            string itemClassName = string.Empty;
            string itemClassOverName = string.Empty;

            ItemClass itemClass = GetLevelItemClass(level);

            if (menuItem.Enabled)
            {                
                if (!menuItem.Selected)
                {
                    itemClassName = (menuItem.CssClass.Length > 0 || itemClass == null)
                        ? menuItem.CssClass : itemClass.Normal;
                                        
                    itemClassOverName = (menuItem.CssClassOver.Length > 0 || itemClass == null)
                        ? menuItem.CssClassSelectedOver : itemClass.Over;

                    // if class not set at all, use default class from the default stylesheet
                    if (string.IsNullOrEmpty(this.StyleSheetUrl))
                    {
                        if (itemClassName.Length == 0)
                            itemClassName = "MenuItem";

                        if (itemClassOverName.Length == 0)
                            itemClassOverName = "MenuItemOver";
                    }
                }
                else
                {
                    itemClassName = (menuItem.CssClassSelected.Length > 0 || itemClass == null)
                        ? menuItem.CssClassSelected : itemClass.Selected;

                    itemClassOverName = (menuItem.CssClassSelectedOver.Length > 0 || itemClass == null)
                        ? menuItem.CssClassSelectedOver : itemClass.SelectedOver;

                    // if class not set at all, use default class from the default stylesheet
                    if (string.IsNullOrEmpty(this.StyleSheetUrl))
                    {
                        if (itemClassName.Length == 0)
                            itemClassName = "MenuItemSelected";

                        if (itemClassOverName.Length == 0)
                            itemClassOverName = "MenuItemSelectedOver";
                    }
                }
            }
            else
            {
                itemClassName = (menuItem.CssClassDisabled.Length > 0 || itemClass == null)
                    ? menuItem.CssClassDisabled : itemClass.Disabled;

                itemClassOverName = string.Empty;

                // if class not set at all, use default class from the default stylesheet
                if (string.IsNullOrEmpty(this.StyleSheetUrl))
                {
                    if (itemClassName.Length == 0)
                        itemClassName = "MenuItemDisabled";
                }
            }

            return new string[] { itemClassName, itemClassOverName };
        }

        private ImageMouseoverInfo GetItemCurrentAndHoverLeftIcons(MultiMenuItem menuItem, int level)
        {
            // check menuItem for css classes first
            // if class it not set on the menuItem level, check the default menu property
            string itemIconName = string.Empty;
            string itemIconOverName = string.Empty;

            MenuMarker marker = menuItem.ParentMenuGroup.LeftMenuMarkerForItemsToUse;
            ImageMouseoverInfo mouseoverInfo = new ImageMouseoverInfo();
            
            if (menuItem.Enabled)
            {
                // menuItem is in a subgroup
                if (!menuItem.Selected)
                {
                    mouseoverInfo.Url = (menuItem.LeftIcon.Length > 0 || marker == null)
                        ? menuItem.LeftIcon : marker.Url;

                    mouseoverInfo.OverUrl = (menuItem.LeftIconOver.Length > 0 || marker == null)
                        ? menuItem.LeftIconOver : marker.OverUrl;                    
                }
                else
                {
                    mouseoverInfo.Url = (menuItem.LeftIconSelected.Length > 0 || marker == null)
                        ? menuItem.LeftIconSelected : marker.SelectedUrl;

                    mouseoverInfo.OverUrl = (menuItem.LeftIconSelectedOver.Length > 0 || marker == null)
                        ? menuItem.LeftIconSelectedOver : marker.SelectedUrl;                   
                }
            }
            else
            {
                mouseoverInfo.Url = (menuItem.LeftIconDisabled.Length > 0 || marker == null)
                    ? menuItem.LeftIconDisabled : marker.DisabledUrl;

                mouseoverInfo.OverUrl = string.Empty;           
            }

            // set height and width
            mouseoverInfo.Height = (menuItem.LeftIconHeight > 0 || marker == null)
                ? menuItem.LeftIconHeight : marker.Height;

            mouseoverInfo.Width = (menuItem.LeftIconWidth > 0 || marker == null)
                ? menuItem.LeftIconWidth : marker.Width;

            return mouseoverInfo;
        }
        private ImageMouseoverInfo GetItemCurrentAndHoverRightIcons(MultiMenuItem menuItem, int level)
        {
            // check menuItem for css classes first
            // if class it not set on the menuItem level, check the default menu property
            string itemIconName = string.Empty;
            string itemIconOverName = string.Empty;

            MenuMarker marker = menuItem.ParentMenuGroup.RightMenuMarkerForItemsToUse;
            ImageMouseoverInfo mouseoverInfo = new ImageMouseoverInfo();

            if (menuItem.Enabled)
            {
                // menuItem is in a subgroup
                if (!menuItem.Selected)
                {
                    mouseoverInfo.Url = (menuItem.RightIcon.Length > 0 || marker == null)
                        ? menuItem.RightIcon : marker.Url;

                    mouseoverInfo.OverUrl = (menuItem.RightIconOver.Length > 0 || marker == null)
                        ? menuItem.RightIconOver : marker.OverUrl;
                }
                else
                {
                    mouseoverInfo.Url = (menuItem.RightIconSelected.Length > 0 || marker == null)
                        ? menuItem.RightIconSelected : marker.SelectedUrl;

                    mouseoverInfo.OverUrl = (menuItem.RightIconSelectedOver.Length > 0 || marker == null)
                        ? menuItem.RightIconSelectedOver : marker.SelectedOverUrl;
                }
            }
            else
            {
                mouseoverInfo.Url = (menuItem.RightIconDisabled.Length > 0 || marker == null)
                    ? menuItem.RightIconDisabled : marker.DisabledUrl;

                mouseoverInfo.OverUrl = string.Empty;
            }

            // set height and width
            mouseoverInfo.Height = (menuItem.RightIconHeight > 0 || marker == null)
                ? menuItem.RightIconHeight : marker.Height;

            mouseoverInfo.Width = (menuItem.RightIconWidth > 0 || marker == null)
                ? menuItem.RightIconWidth : marker.Width;

            return mouseoverInfo;
        }
        
        private string[] GetIds(MultiMenuItem menuItem, int level, int position)
        {
            string itemID = string.Empty;
            string groupID = string.Empty;

            if (level > 0)
            {
                itemID = menuItem.ParentMenuGroup.ParentMenuItem.Id + "_" + position.ToString();
            }
            else
            {
                itemID = this.UniqueID + "_itm_" + position.ToString();
            }

            if (menuItem.SubMenuGroup != null)
            {
                if (this.MultiMenuType == MenuType.Horizontal && level == 0)
                {
                    groupID = itemID.Replace("_itm_", "_grp_");
                }
                else
                {
                    if (menuItem.SubMenuGroup.ExpandDirection == ExpandDirectionType.Right)
                    {
                        groupID = itemID.Replace("_itm_", "_rgrp_");
                    }
                    else if (menuItem.SubMenuGroup.ExpandDirection == ExpandDirectionType.Left)
                    {
                        groupID = itemID.Replace("_itm_", "_lgrp_");
                    }
                    else
                    {
                        groupID = itemID.Replace("_itm_", "_grp_");
                    }
                }
            }

            return new string[] { itemID, groupID };
        }

        private int GetTabStop(MultiMenuItem menuItem)
        {
            //if (menuItem != null && isFireFox15Request && menuItem.ParentMenuGroup.Id != TopMenuGroup.Id)
            //    return -1;

            if (menuItem != null && (menuItem.IsDivider || !menuItem.Enabled))
                return -1;

            if (this._tabStop > -1)
            {
                this._tabStop++;
            }
            return this._tabStop;
        }
        public MultiMenuItem FindMenuItem(string tag)
        {
            bool match = false;
            return this.FindMenuItemByTag(this._topMenuGroup, -1, ref tag, ref match);
        }
        private MultiMenuItem FindMenuItemByTag(MultiMenuGroup menuGroup, int level, ref string tag, ref bool matchFlag)
        {
            level++;

            for (int i = 0; i < menuGroup.MultiMenuItems.Count; i++)
            {
                MultiMenuItem menuItem = menuGroup.MultiMenuItems[i];

                if (menuItem.Tag.Length > 0 && menuItem.Tag.Equals(tag))
                {
                    matchFlag = true; // return match
                    return menuItem;
                }

                if (menuItem.SubMenuGroup != null && menuItem.SubMenuGroup.MultiMenuItems.Count > 0)
                {
                    menuItem = this.FindMenuItemByTag(menuItem.SubMenuGroup, level, ref tag, ref matchFlag); // search deeper
                }

                if (matchFlag == true)
                    return menuItem;
            }
            return null;
        }

        private string GetStylesheetUrl()
        {
            string stylesheetPath;

            if (!this.DesignMode && string.IsNullOrEmpty(this.StyleSheetUrl))
            {
                stylesheetPath = Page.ClientScript.GetWebResourceUrl(
                    this.GetType(), _styleResourcePrefix + ".DefaultStyle.css");
            }
            else
            {
                stylesheetPath = this.GetResolvedPath(this.StyleSheetUrl, ResourceType.StyleSheet, (this.DesignMode));
            }
            return stylesheetPath;
        }
        private string GetResolvedPath(string path, ResourceType typeOfResource, bool mapPathToFileSystem)
        {
            if (!string.IsNullOrEmpty(path))
            {
                if (!this.DesignMode)
                {                   
                    if (mapPathToFileSystem && (path.StartsWith("~") || path.StartsWith("/")))
                    {
                        path = Page.Server.MapPath(path);
                    }
                    else if (path.StartsWith("~"))
                    {
                        path = Page.ResolveUrl(path);
                    }
                    else if (path.StartsWith("."))
                    {
                        // get path for assembly based resource
                        path = Page.ClientScript.GetWebResourceUrl(this.GetType(), _imageResourcePrefix + path);
                    }
                    else
                    {
                        if (typeOfResource == ResourceType.Image && !string.IsNullOrEmpty(this.ImagesUrlBase) && !path.StartsWith("~"))
                            path = this.ImagesUrlBase + path;

                        if (path.StartsWith("~"))
                            path = Page.ResolveUrl(path);
                    }
                }
                else
                {
                    if (path.StartsWith("."))
                    {
                        // get path for assembly based resource
                        path = Page.ClientScript.GetWebResourceUrl(this.GetType(), _imageResourcePrefix + path);
                    }
                    else
                    {
                        if (typeOfResource == ResourceType.Image && !string.IsNullOrEmpty(this.ImagesUrlBase) && !path.StartsWith("~"))
                        {
                            path = this.ImagesUrlBase + path;

                            if (!(path.StartsWith("~") || path.StartsWith("/")))
                            {
                                // path is relative. no need to add the base path.
                                return path;
                            }
                        }

                        if (path.StartsWith("~"))
                            path = path.Substring(1);

                        if (path.StartsWith("/"))
                            path = path.Substring(1);

                        path = DesignTimeBasePath + path.Replace("/", @"\");
                    }
                }
            }

            //this.DesignDebugInfo += path + "<br>";
            return path;
        }

        public MultiMenuItems GetSelectedItems()
        {
            MultiMenuItems selectedMenuItems = new MultiMenuItems();
            this.GetSelectedItems(_topMenuGroup, selectedMenuItems);
            return selectedMenuItems;
        }
        private void GetSelectedItems(MultiMenuGroup parentMenuGroup, MultiMenuItems selectedMenuItems)
        {
            // recursive loop to pick out all the selected menu itmes
            for (int i = 0; i < parentMenuGroup.MultiMenuItems.Count; i++)
            {
                if (parentMenuGroup.MultiMenuItems[i].Selected)
                {
                    int index = selectedMenuItems.Add(parentMenuGroup.MultiMenuItems[i]); // this will clear the items's parent
                    selectedMenuItems[index].ParentMenuGroup = parentMenuGroup; // add it back here
                }

                if (parentMenuGroup.MultiMenuItems[i].SubMenuGroup != null
                    && parentMenuGroup.MultiMenuItems[i].SubMenuGroup.MultiMenuItems.Count > 0)

                    this.GetSelectedItems(parentMenuGroup.MultiMenuItems[i].SubMenuGroup, selectedMenuItems);
            }
        }

        public void ClearSelectedItems()
        {
            this.ClearSelectedItems(_topMenuGroup);
        }
        private void ClearSelectedItems(MultiMenuGroup parentMenuGroup)
        {
            // recursive loop to clear all the selected menu itmes
            for (int i = 0; i < parentMenuGroup.MultiMenuItems.Count; i++)
            {
                parentMenuGroup.MultiMenuItems[i].Selected = false;

                if (parentMenuGroup.MultiMenuItems[i].SubMenuGroup != null
                    && parentMenuGroup.MultiMenuItems[i].SubMenuGroup.MultiMenuItems.Count > 0)

                    this.ClearSelectedItems(parentMenuGroup.MultiMenuItems[i].SubMenuGroup);
            }
        }

        #endregion

        #region Serialization and Deserialization
        public void GetXmlRepresentation(XmlTextWriter writer)
        {
            this.Serialize(writer, this._topMenuGroup, -1, false);
        }
        public string GetXmlRepresentation(bool enableOutlineFormatting)
        {
            string xml = string.Empty;

            using (StringWriter stringWriter = new StringWriter())
            {
                XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);

                if (enableOutlineFormatting)
                {
                    xmlWriter.Formatting = Formatting.Indented;
                    xmlWriter.Indentation = 4;
                    xmlWriter.IndentChar = ' ';
                }

                this.GetXmlRepresentation(xmlWriter);
                xml = stringWriter.ToString();
            }

            return xml;
        }

        private void Serialize(XmlTextWriter writer, MultiMenuGroup menuGroup, int level, bool compress)
        {
            level++; // increment level

            // write top group element
            // write top group's attributes
            if (level == 0)
                this._topMenuGroup.Serialize(writer, compress);

            for (int i = 0; i < menuGroup.MultiMenuItems.Count; i++)
            {
                MultiMenuItem menuItem = menuGroup.MultiMenuItems[i];

                // write menuItem begin element
                // write menuItem's attributes
                menuItem.Serialize(writer, compress);

                if (menuItem.SubMenuGroup != null && menuItem.SubMenuGroup.MultiMenuItems.Count > 0)
                {
                    // write group element begin
                    // write group's attributes
                    menuItem.SubMenuGroup.Serialize(writer, compress);

                    this.Serialize(writer, menuItem.SubMenuGroup, level, compress);

                    // close group element
                    writer.WriteEndElement();
                }

                // close menuItem element
                writer.WriteEndElement();
            }

            // close top group element
            if (level == 0)
                writer.WriteEndElement();
        }
        private void Deserialize(XPathNodeIterator iterator, MultiMenuGroup menuGroup, bool isCompressed)
        {
            string path = string.Empty;

            while (iterator.MoveNext())
            {
                XPathNavigator groupNavigator = iterator.Current.Clone();
                menuGroup.DeserializeMenuGroup(menuGroup, groupNavigator, isCompressed);
                OnMenuGroupCreated(new MenuGroupCreatedEventArgs(menuGroup));

                if (iterator.Current.HasChildren)
                {
                    path = (isCompressed) ? "MI" : "MenuItem";
                    XPathNodeIterator itemIterator = iterator.Current.SelectChildren(path, "");

                    while (itemIterator.MoveNext())
                    {
                        MultiMenuItem menuItem = new MultiMenuItem();
                        XPathNavigator itemNavigator = itemIterator.Current.Clone();
                        
                        menuItem.DeserializeMenuItem(menuItem, itemNavigator, isCompressed);
                        OnMenuItemCreated(new MenuItemCreatedEventArgs(menuItem));

                        menuGroup.MultiMenuItems.Add(menuItem);

                        if (itemIterator.Current.HasChildren)
                        {
                            path = (isCompressed) ? "MG" : "MenuGroup";
                            XPathNodeIterator groupIterator = itemIterator.Current.SelectChildren(path, "");

                            MultiMenuGroup subMenuGroup = menuItem.CreateMenuGroup();
                            this.Deserialize(groupIterator, subMenuGroup, isCompressed);
                        }
                    }
                }
            }
        }

        private void DeserializeMenu(string xml, bool isCompressed)
        {
            using (StringReader stringReader = new StringReader(xml))
            {
                this.DeserializeMenu(stringReader, isCompressed);
            }
        }
        private void DeserializeMenu(TextReader reader, bool isCompressed)
        {
            XPathDocument document = new XPathDocument(reader);
            this.DeserializeMenu(document, isCompressed);
        }
        private void DeserializeMenu(string url)
        {
            XPathDocument document = null;
            XmlReader reader = null;

            try
            {
                XmlUrlResolver resolver = new XmlUrlResolver();
                resolver.Credentials = CredentialCache.DefaultCredentials;

                XmlReaderSettings settings = new XmlReaderSettings();
                settings.XmlResolver = resolver;
                reader = XmlReader.Create(url);

                document = new XPathDocument(reader);
            }
            catch (XmlException ex1)
            {
                throw new ApplicationException("The XML cannot be read. Please make sure it is well formed with no reserved characters (like the ampersand) are being used.", ex1);
            }
            catch (WebException ex2)
            {
                throw new ApplicationException("Unable to locate an XML resource at URL: " + url, ex2);
            }
            finally
            {
                if (reader != null)
                {
                    reader.Close();
                    reader = null;
                }
            }

            this.DeserializeMenu(document, false);
        }

        private void DeserializeMenu(XPathDocument document, bool isCompressed)
        {
            XPathNavigator navigator = document.CreateNavigator();

            string path = (isCompressed) ? "/MG" : "/MenuGroup";

            XPathNodeIterator iterator = navigator.Select(path);
            this.Deserialize(iterator, this._topMenuGroup, isCompressed);

            OnXmlLoaded(new EventArgs());
        }
        public XPathDocument BuildMenu(XmlReader reader)
        {
            XPathDocument document = new XPathDocument(reader);
            this.DeserializeMenu(document, false);
            return document;
        }
        public void BuildMenu(XPathDocument document)
        {
            OnXmLoading(new EventArgs());

            XPathNavigator navigator = document.CreateNavigator();

            string path = "/MenuGroup";

            XPathNodeIterator iterator = navigator.Select(path);
            this.Deserialize(iterator, this._topMenuGroup, false);
        }

        internal void LoadXml()
        {
            if (!Page.IsPostBack || !this.EnableViewState)
            {
                OnXmLoading(new EventArgs());

                if (this.XmlPath.Length > 0)
                {
                    string path = this.GetResolvedPath(this.XmlPath, ResourceType.XmlData, true);

                    if (!this.DesignMode && Page.Trace.IsEnabled)
                        Page.Trace.Write(this.ID + " XML Path", path);

                    this.DeserializeMenu(path);
                }
                else if (_xmlDocument != null)
                {
                    this.DeserializeMenu(_xmlDocument, false);
                }
                else if (this.Xml.Length > 0)
                {
                    using (StringReader reader = new StringReader(this.Xml))
                    {
                        this.DeserializeMenu(reader, false);
                    }
                }
            }
        }

        #endregion

        #region View State
        protected override object SaveViewState()
        {
            if (HttpContext.Current == null)
                return null;

            object[] viewState = new object[7];
            string xml = string.Empty;

            using (StringWriter stringWriter = new StringWriter())
            {
                XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);
                this.Serialize(xmlWriter, this._topMenuGroup, -1, true);
                xml = stringWriter.ToString();
            }

            viewState[0] = xml;

            // save group level class collection
            object groupClassState = null;

            if (this._groupClasses != null && this._groupClasses.Count > 0)
                groupClassState = ((IStateManager)this._groupClasses).SaveViewState();

            viewState[1] = groupClassState;

            // save item level class collection
            object itemClassState = null;

            if (this._itemClasses != null && this._itemClasses.Count > 0)
                itemClassState = ((IStateManager)this._itemClasses).SaveViewState();

            viewState[2] = itemClassState;

            // save context menu hotspot collection
            object hotSpotState = null;

            if (this._contextHotSpots != null && this._contextHotSpots.Count > 0)
                hotSpotState = ((IStateManager)this._contextHotSpots).SaveViewState();

            viewState[3] = hotSpotState;

            // save left menu marker collection
            object leftMenuMarkers = null;

            if (this._leftMenuMarkers != null && this._leftMenuMarkers.Count > 0)
                leftMenuMarkers = ((IStateManager)this._leftMenuMarkers).SaveViewState();

            viewState[4] = leftMenuMarkers;

            // save right menu marker collection
            object rightMenuMarkers = null;

            if (this._rightMenuMarkers != null && this._rightMenuMarkers.Count > 0)
                rightMenuMarkers = ((IStateManager)this._rightMenuMarkers).SaveViewState();

            viewState[5] = rightMenuMarkers;

            viewState[6] = base.SaveViewState();

            return viewState;
        }
        protected override void LoadViewState(object savedState)
        {
            object[] viewState = (object[])savedState;

            this.DeserializeMenu((string)viewState[0], true);

            if (viewState[1] != null)
                ((IStateManager)this.LevelGroupClasses).LoadViewState(viewState[1]);

            if (viewState[2] != null)
                ((IStateManager)this.LevelItemClasses).LoadViewState(viewState[2]);

            if (viewState[3] != null)
                ((IStateManager)this.ContextHotSpots).LoadViewState(viewState[3]);

            if (viewState[4] != null)
                ((IStateManager)this.LevelLeftMenuMarkers).LoadViewState(viewState[4]);

            if (viewState[5] != null)
                ((IStateManager)this.LevelRightMenuMarkers).LoadViewState(viewState[5]);

            base.LoadViewState(viewState[6]);
        }
        protected override void TrackViewState()
        {
            base.TrackViewState();

            if (this._contextHotSpots != null)
            {
                ((IStateManager)this._contextHotSpots).TrackViewState();
            }
            if (this._groupClasses != null)
            {
                ((IStateManager)this._groupClasses).TrackViewState();
            }
            if (this._itemClasses != null)
            {
                ((IStateManager)this._itemClasses).TrackViewState();
            }
            if (this._leftMenuMarkers != null)
            {
                ((IStateManager)this._leftMenuMarkers).TrackViewState();
            }
            if (this._rightMenuMarkers != null)
            {
                ((IStateManager)this._rightMenuMarkers).TrackViewState();
            }
        }
        #endregion

        #region Post Back Methods
        private void OnMenuItemClicked(MenuItemClickedEventArgs e)
        {
            ItemClickedHandler menuItemClickedEventDelegate = (ItemClickedHandler)Events[menuItemClickedEvent];

            if (menuItemClickedEventDelegate != null)
            {
                menuItemClickedEventDelegate(this, e);
            }
        }
        public void RaisePostBackEvent(string eventArgument)
        {
            this.SetSelectedItem(this._topMenuGroup, -1, eventArgument);
        }
        #endregion

        #region Properties

        [XmlElement("TopMenuGroup")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public MultiMenuGroup TopMenuGroup
        {
            get { return this._topMenuGroup; }
            set { this._topMenuGroup = value; }
        }

        [Obsolete("Use method GetSelectedItems instead.")]
        public MultiMenuItem SelectedItem
        {
            get { return this._selectedMenuItem; }
        }

        internal string DesignTimeBasePath
        {
            get { return this._designTimeBasePath; }
            set { this._designTimeBasePath = value; }
        }

        #region Behavior
        [Bindable(false), Category("Behavior"), DefaultValue(""), Description("Ids of elements that context menu will appear over.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty)]
        [Editor(typeof(ContextHotSpotCollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public ContextHotSpotCollection ContextHotSpots
        {
            get
            {
                if (_contextHotSpots == null)
                {
                    this._contextHotSpots = new ContextHotSpotCollection();

                    if (IsTrackingViewState)
                    {
                        ((IStateManager)this._contextHotSpots).TrackViewState();
                    }
                }

                return this._contextHotSpots;
            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(false), Description("This property no longer has any effect and will be removed in a future version. To enable a hyperlink for a menu item, set the URL and PostBack to False.")]
        [Obsolete("This property no longer has any effect and will be removed in a future version. To enable a hyperlink for a menu item, set the URL and PostBack to False.")]
        public bool EnableHyperlinks
        {
            get
            {
                return false;
            }
            set
            {

            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(false), Description("Use QueryPassId property instead. This property will be removed in a future version.")]
        [Obsolete("Use EnableMultiPageState property instead. This property will be removed in a future version.")]
        public bool PassIdOnQueryString
        {
            get
            {
                return this._enableMultiPageState;
            }
            set
            {
                this._enableMultiPageState = value;
            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(false), Description("To persist currently selected item between pages (non-post back).")]
        public bool EnableMultiPageState
        {
            get
            {
                return this._enableMultiPageState;
            }
            set
            {
                this._enableMultiPageState = value;
            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(false), Description("Enables encryption for url query strings.")]
        public bool EncryptQueryString
        {
            get
            {
                return this._encryptQueryString;
            }
            set
            {
                this._encryptQueryString = value;
            }
        }

        [Bindable(false), Category("Behavior"), DefaultValue(""), Description("Key used to encrypt url query strings.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
        public string EncryptionKey
        {
            get
            {
                return this._encryptionKey;
            }
            set
            {
                this._encryptionKey = value;
            }
        }
        
        [Bindable(true), Category("Behavior"), DefaultValue("mmid"), Description("The key used for passing the selected item ID on the query string.")]
        public string QuerySelectedItemKey
        {
            get
            {
                return this._queryStringSelectedItemKey;
            }
            set
            {
                this._queryStringSelectedItemKey = value;
            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(""), Description("A delimeted list of query string parameters to pick up from the request object and append to each menu item's URL.")]
        public string QueryParamsToPass
        {
            get
            {
                return this._queryStringParamsToPass;
            }
            set
            {
                this._queryStringParamsToPass = value;
            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(""), Description("A formatted query string to append to each menu item's URL.")]
        public string QueryDataToAdd
        {
            get
            {
                return this._queryStringDataToAdd;
            }
            set
            {
                this._queryStringDataToAdd = value;
            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(UniqueIdType.Id), Description("The Id type passed on the query string used for maintaining selection state.")]
        public UniqueIdType QueryUniqueIdType
        {
            get
            {
                return this._idType;
            }
            set
            {
                this._idType = value;
            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(true), Description("Enables menu markers for the top level items.")]
        public bool EnableTopMenuMarkers
        {
            get
            {
                object o = ViewState["EnableTopMenuMarkers"];

                return (o == null) ? true : (bool)o;
            }
            set
            {
                ViewState["EnableTopMenuMarkers"] = value;
            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(false), Description("Enables tool tips on menu items.")]
        public bool EnableToolTips
        {
            get
            {
                object o = ViewState["EnableToolTips"];

                return (o == null) ? false : (bool)o;
            }
            set
            {
                ViewState["EnableToolTips"] = value;
            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(false), Description("Delay (in milliseconds) prior to displaying tool tips.")]
        public int ToolTipDelay
        {
            get
            {
                object o = ViewState["ToolTipDelay"];

                return (o == null) ? 1500 : (int)o;
            }
            set
            {
                ViewState["ToolTipDelay"] = value;
            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(false), Description("Duration of time (in milliseconds) tool tips are displayed.")]
        public int ToolTipDuration
        {
            get
            {
                object o = ViewState["ToolTipDuration"];

                return (o == null) ? 4000 : (int)o;
            }
            set
            {
                ViewState["ToolTipDuration"] = value;
            }
        }

        [Bindable(false), Category("Behavior"), DefaultValue(MenuType.Horizontal), Description("Sets the type of menu to display.")]
        public MenuType MultiMenuType
        {
            get
            {
                return this._menuType;
            }
            set
            {
                this._menuType = value;
            }
        }

        [Bindable(true), Category("Behavior"), DefaultValue(""), Description("When hyperlinks are enabled, allows specific frames to receive URL.")]
        public string Target
        {
            get
            {
                object o = ViewState["Target"];
                return ((o == null) ? String.Empty : (string)o);
            }
            set
            {
                ViewState["Target"] = value;
            }
        }

        #endregion

        #region Appearance
        [Bindable(false), Category("Appearance"), DefaultValue(""), Description("CSS Style classes to apply to menu items for each level.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty)]
        [Editor(typeof(ItemClassCollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public ItemClassCollection LevelItemClasses
        {
            get
            {
                if (this._itemClasses == null)
                {
                    this._itemClasses = new ItemClassCollection();

                    if (IsTrackingViewState)
                    {
                        ((IStateManager)this._itemClasses).TrackViewState();
                    }
                }

                return this._itemClasses;
            }
        }

        [Bindable(false), Category("Appearance"), DefaultValue(""), Description("CSS Style classes to apply to menu group on each level.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty)]
        [Editor(typeof(GroupClassCollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public GroupClassCollection LevelGroupClasses
        {
            get
            {
                if (this._groupClasses == null)
                {
                    this._groupClasses = new GroupClassCollection();

                    if (IsTrackingViewState)
                    {
                        ((IStateManager)this._groupClasses).TrackViewState();
                    }
                }

                return this._groupClasses;
            }
        }

        [Bindable(false), Category("Appearance"), DefaultValue(""), Description("Images to use to indicate a menu item contains a child menu.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty)]
        [Editor(typeof(MenuMarkerCollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public MenuMarkerCollection LevelRightMenuMarkers
        {
            get
            {
                if (this._rightMenuMarkers == null)
                {
                    this._rightMenuMarkers = new MenuMarkerCollection();

                    if (IsTrackingViewState)
                    {
                        ((IStateManager)this._rightMenuMarkers).TrackViewState();
                    }
                }

                return this._rightMenuMarkers;
            }
        }

        [Bindable(false), Category("Appearance"), DefaultValue(""), Description("Images to use to indicate a menu item contains a child menu.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty)]
        [Editor(typeof(MenuMarkerCollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public MenuMarkerCollection LevelLeftMenuMarkers
        {
            get
            {
                if (this._leftMenuMarkers == null)
                {
                    this._leftMenuMarkers = new MenuMarkerCollection();

                    if (IsTrackingViewState)
                    {
                        ((IStateManager)this._leftMenuMarkers).TrackViewState();
                    }
                }

                return this._leftMenuMarkers;
            }
        }

        [Bindable(true), Category("Appearance"), DefaultValue(false), Description("How selection CSS classes are applied to menu items and their parent nodes.")]
        public SelectionHighlightType SelectionHighlight
        {
            get
            {
                object o = ViewState["SelectionHighlight"];

                return (o == null) ? SelectionHighlightType.ItemOnly : (SelectionHighlightType)o;
            }
            set
            {
                ViewState["SelectionHighlight"] = value;
            }
        }

        [Bindable(true), Category("Appearance"), DefaultValue(""), Description("Base URL for menu's images.")]
        public string ImagesUrlBase
        {
            get
            {
                object o = ViewState["ImagesBaseUrl"];
                return ((o == null) ? String.Empty : (string)o);
            }
            set
            {
                string baseUrl = value.Trim();

                if (baseUrl.Length > 0 && !baseUrl.EndsWith("/"))
                    baseUrl += "/";

                ViewState["ImagesBaseUrl"] = baseUrl;
            }
        }

        [Bindable(true), Category("Appearance"), DefaultValue(""), Description("URL to menu's style sheet.")]
        [EditorAttribute(typeof(System.Web.UI.Design.UrlEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public string StyleSheetUrl
        {
            get
            {
                object o = ViewState["StyleUrl"];
                return ((o == null) ? String.Empty : (string)o);
            }
            set
            {
                ViewState["StyleUrl"] = value;
            }
        }

        [Bindable(true), Category("Appearance"), DefaultValue("{0} Menu"), Description("Alternate text for submenu indicator icon.")]
        public string SubMenuMarkerTextFormat
        {
            get
            {
                object o = ViewState["SubMenuMarkerText"];
                return ((o == null) ? "{0} Menu" : (string)o);
            }
            set
            {
                ViewState["SubMenuMarkerText"] = value;
            }
        }

        [Bindable(true), Category("Appearance"), DefaultValue(true), Description("Width of menu.")]
        public int Width
        {
            get
            {
                object o = ViewState["Width"];
                return ((o == null) ? -1 : (int)o);
            }
            set
            {
                ViewState["Width"] = value;
            }
        }

        [Bindable(true), Category("Appearance"), DefaultValue(true), Description("Height of menu.")]
        public int Height
        {
            get
            {
                object o = ViewState["Height"];
                return ((o == null) ? -1 : (int)o);
            }
            set
            {
                ViewState["Height"] = value;
            }
        }

        [Bindable(true), Category("Appearance"), DefaultValue(true), Description("Indicates if breaks are allowed between words.")]
        public bool WordWrap
        {
            get
            {
                object o = ViewState["WordWrap"];

                if (o != null)
                    return (bool)o;
                else
                    return true;
            }
            set
            {
                ViewState["WordWrap"] = value;
            }
        }

        #endregion

        #region Misc
        [Bindable(true), Category("Misc"), DefaultValue(""), Description("XML used to populate menu.")]
        [Editor(typeof(System.ComponentModel.Design.MultilineStringEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public string Xml
        {
            get { return this._xml; }
            set { this._xml = value; }
        }

        [Bindable(true), Category("Misc"), DefaultValue(""), Description("URL or file path to XML file for populating menu.")]
        [EditorAttribute(typeof(System.Web.UI.Design.UrlEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public string XmlPath
        {
            get { return this._xmlPath; }
            set { this._xmlPath = value; }
        }

        [Browsable(false)]
        [Bindable(false), Category("Misc"), DefaultValue(""), Description("XML used to populate menu.")]
        public XPathDocument XmlDocument
        {
            get { return _xmlDocument; }
            set { _xmlDocument = value; }
        }
        #endregion

        #region Accessibility
        [Bindable(true), Category("Accessibility"), DefaultValue(false), Description("Enables keyboard support for complete navigation.")]
        public bool EnableKeyboard
        {
            get
            {
                object o = ViewState["EnableKeyboard"];

                return (o == null) ? true : (bool)o;
            }
            set
            {
                ViewState["EnableKeyboard"] = value;
            }
        }

        [Bindable(true), Category("Accessibility"), DefaultValue(false), Description("Starting tab position for menu items.")]
        public int TabStartIndex
        {
            get
            {
                object o = ViewState["TabStartIndex"];
                return ((o == null) ? 1 : (int)o);
            }
            set
            {
                ViewState["TabStartIndex"] = value;
            }
        }

        [Bindable(true), Category("Accessibility"), DefaultValue("Skip Menu Navigation"), Description("Text to use for menu skip link, used by page readers.")]
        public string SkipLinkText
        {
            get
            {
                object o = ViewState["SkipLinkText"];
                return ((o == null) ? string.Empty : (string)o);
            }
            set
            {
                ViewState["SkipLinkText"] = value;
            }
        }

        [Bindable(true), Category("Accessibility"), DefaultValue("{0}"), Description("Text to use for the currently selected menu item.")]
        public string CurrentItemText
        {
            get
            {
                object o = ViewState["CurrentItemText"];
                return ((o == null) ? "{0}" : (string)o);
            }
            set
            {
                ViewState["CurrentItemText"] = value;
            }
        }

        [Bindable(true), Category("Accessibility"), DefaultValue(false), Description("Enables menu skip link, useful for page readers.")]
        public bool EnableSkipLink
        {
            get
            {
                object o = ViewState["EnableSkipLink"];

                if (o != null)
                    return (bool)o;
                else
                    return false;
            }
            set
            {
                ViewState["EnableSkipLink"] = value;
            }
        }

        [Bindable(true), Category("Accessibility"), DefaultValue(""), Description("Keyboard access key.")]
        //[DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true)]
        public AccessKeyType AccessKey
        {
            get
            {
                object o = ViewState["AccessKeyType"];
                return ((o == null) ? AccessKeyType.M : (AccessKeyType)o);
            }
            set
            {
                ViewState["AccessKeyType"] = value;
            }
        }

        [Bindable(true), Category("Accessibility"), DefaultValue(""), Description("Keyboard access key modifier.")]
        public AccessKeyModifierType AccessKeyModifier
        {
            get
            {
                object o = ViewState["AccessKeyModifierType"];
                return ((o == null) ? AccessKeyModifierType.Alt : (AccessKeyModifierType)o);
            }
            set
            {
                ViewState["AccessKeyModifierType"] = value;
            }
        }

        #endregion

        #endregion
    }
}
